package ControllerApplication;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import ModelApplication.Sound;
import ProcessingAudioWindows.AudioProcessingWindowLocal;
import ProcessingAudioWindows.F2AudioProcessingWindow;
import ProcessingAudioWindows.T1AudioProcessingWindow;
import UtilsApplication.ChartTypes;
import UtilsApplication.DetectNotes;
import UtilsApplication.HandleAudioUtil;

public class T1AudioProcessing extends AudioProcessing {

	double a[] = { 0, 0 };
	double b[] = { 0, 0, 0 };
	double mem1[] = { 0, 0, 0, 0 };
	double mem2[] = { 0, 0, 0, 0 };
	
	boolean isFilterActive = false;
	double sRate = 300;
	
	public T1AudioProcessing(String methodName,
			AudioProcessingWindowLocal imgWindow) {
		super(methodName, imgWindow);

	}

	@Override
	public List<Sound> processingList(Sound sound) {

		T1AudioProcessingWindow imgW = (T1AudioProcessingWindow) imgWindow;
		List<Sound> sounds = new ArrayList<Sound>();
		double[] origAudio = sound.getAudio();
		int zcrMethod = 0;
		String methodName = "";
		isFilterActive = imgW.isFIlterActive();
		sRate = imgW.getSRate();
	
		if (imgW.getOption1().isSelected()) {
			zcrMethod = 1;
			methodName = "Analiza przejść przez zero";
			if(isFilterActive) {
				methodName = "Analiza przejść przez zero (po filtracji)";
			}
		} else if (imgW.getOption2().isSelected()) {
			zcrMethod = 2;
			methodName = "Zliczanie maksimów";
			if(isFilterActive) {
				methodName = "Zliczanie maksimów (po filtracji)";
			}
		} else if (imgW.getOption3().isSelected()) {
			zcrMethod = 3;
			methodName = "Analiza przejść przez wartość progową";
			if(isFilterActive) {
				methodName = "Analiza przejść przez wartość progową (po filtracji)";
			}
			
		}
		// wykrywanie tonow w sekwencji
		if (imgW.isDetectNotes()) {
			sounds.addAll(DetectNotes.detectNotes(sound, this, zcrMethod));
		} else {

			double[] audio = Arrays.copyOfRange(origAudio,
					origAudio.length / 2, origAudio.length / 2
							+ (int) sound.getWavFile().getSampleRate() / 20);

			double freq = findFundamentalFreq(sounds, audio, sound, zcrMethod);

			double[] newAudio = HandleAudioUtil.createTone(freq,
					origAudio.length, (int) sound.getWavFile().getSampleRate());

			Sound zcrSound = new Sound(newAudio, sound.getWavFile(), null,
					methodName, sound.getName(), "wygenerowanyDzwiek",
					ChartTypes.VALUE_VALUE);
			zcrSound.setBaseFreq(freq);

			sounds.add(zcrSound);
		}

		return sounds;
	}

	public double findFundamentalFreq(List<Sound> sounds, double[] audio,
			Sound sound, int zcrMethod) {

		if (zcrMethod == 1) {
			return zeroCrossingRate(sound, audio);

		} else if (zcrMethod == 2) {
			return peakRate(sound, audio);
		} else if (zcrMethod == 3) {
			return zeroCrossingRateThreshold(sound, audio);
		}

		return 0;
	}

	private double zeroCrossingRate(Sound sound, double[] audio) {

		if(isFilterActive) {
			computeSecondOrderLowPassParameters(sound.getWavFile().getSampleRate(),
					1500, a, b);

			for (int j = 0; j < audio.length; ++j) {
				audio[j] = processSecondOrderFilter(audio[j], mem1, a, b);
				audio[j] = processSecondOrderFilter(audio[j], mem2, a, b);
			}
		}
		
		int signChanges = 0;
		for (int i = 1; i < audio.length - 1; i++) {

			if (audio[i] * audio[i - 1] < 0) {
				signChanges++;

			}
		}

		return 1 / (((audio.length - 1) / (double) sound.getWavFile()
				.getSampleRate()) / signChanges) / 2;

	}

	private double zeroCrossingRateThreshold(Sound sound, double[] audio) {

		if(isFilterActive) {
			computeSecondOrderLowPassParameters(sound.getWavFile().getSampleRate(),
					1500, a, b);

			for (int j = 0; j < audio.length; ++j) {
				audio[j] = processSecondOrderFilter(audio[j], mem1, a, b);
				audio[j] = processSecondOrderFilter(audio[j], mem2, a, b);
			}
		}
		
		
		double max = audio[0];
		double min = audio[0];
		for (int i = 1; i < audio.length; i++) {
			if (audio[i] > max) {
				max = audio[i];
			}
			
			if (audio[i] < min) {
				min = audio[i];
			}
		}

		double threshold = 0.8 * max;

		System.out.println(threshold);
		int thresholdCrossing = 0;

		for (int i = 1; i < audio.length - 1; i++) {

			if ((audio[i - 1] < threshold && audio[i] > threshold)
					|| (audio[i - 1] > threshold && audio[i] < threshold)) {
				thresholdCrossing++;

			}
		}

		return 1 / (((audio.length - 1) / (double) sound.getWavFile()
				.getSampleRate()) / thresholdCrossing) / 2;

	}

	private double peakRate(Sound sound, double[] audio) {

		if(isFilterActive) {
			computeSecondOrderLowPassParameters(sound.getWavFile().getSampleRate(),
					330, a, b);

			for (int j = 0; j < audio.length; ++j) {
				audio[j] = processSecondOrderFilter(audio[j], mem1, a, b);
				audio[j] = processSecondOrderFilter(audio[j], mem2, a, b);
			}
		}

		int peaks = 0;
		int range = 10;
		for (int i = range; i < audio.length - range; i++) {

			boolean check = checkMaximum(audio, i, range);
			if (check) {
				peaks++;
			}
		}

		return 1 / ((audio.length / (double) sound.getWavFile().getSampleRate()) / peaks);
	}

	private boolean checkMaximum(double[] audio, int index, int range) {

		for (int i = index - range; i < index + range; i++) {
			if (i != index && audio[i] > audio[index]) {
				return false;
			}
		}

		return true;
	}

	private void computeSecondOrderLowPassParameters(double srate, double f,
			double[] a, double[] b) {
		double a0;
		double w0 = (2 * Math.PI * f / srate);
		double cosw0 = Math.cos(w0);
		double sinw0 = Math.sin(w0);
		double alpha = sinw0 / 2 * Math.sqrt(2);

		a0 = 1 + alpha;
		a[0] = (-2 * cosw0) / a0;
		a[1] = (1 - alpha) / a0;
		b[0] = ((1 - cosw0) / 2) / a0;
		b[1] = (1 - cosw0) / a0;
		b[2] = b[0];
	}

	private double processSecondOrderFilter(double x, double[] mem, double[] a,
			double[] b) {
		double ret = b[0] * x + b[1] * mem[0] + b[2] * mem[1] - a[0] * mem[2]
				- a[1] * mem[3];

		mem[1] = mem[0];
		mem[0] = x;
		mem[3] = mem[2];
		mem[2] = ret;

		return ret;
	}

}
